/**
 * Copyright 2008 Mathias Doenitz, http://lis.to/
 *
 * This file is part of the lis.to java desktop client. The lis.to java desktop client is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * The lis.to java desktop client is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with the lis.to java desktop client.
 * If not, see http://www.gnu.org/licenses/
 */

package listo.client.dialogs;

import listo.client.ImageProvider;
import listo.client.MainForm;
import listo.client.dialogs.controllers.ComponentController;
import listo.utils.reflection.Reflector;
import listo.utils.types.Copyable;

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.*;
import java.util.HashSet;
import java.util.Set;

public abstract class BaseDialog<T extends Copyable<T>> extends JDialog
        implements ComponentController.ModelChangedListener {

    public interface OkListener<T extends Copyable<T>> {
        public void onOk(T modelObject);
    }

    public interface OkCancelListener<T extends Copyable<T>> extends OkListener<T> {
        public void onCancel(T modelObject);
    }

    protected final ImageProvider imageProvider;
    protected final MainForm mainForm;
    protected final Reflector reflector;
    protected OkListener<T> listener;

    protected JPanel contentPane;
    protected GroupLayout layout;
    protected JButton buttonOk;
    protected JButton buttonCancel;
    protected JComponent firstFocusComponent;

    protected T modelObject;

    protected final Set<ComponentController> invalidControllers = new HashSet<ComponentController>();

    public BaseDialog(MainForm mainForm, ImageProvider imageProvider, Reflector reflector) {
        super(mainForm);
        this.reflector = reflector;
        this.mainForm = mainForm;
        this.imageProvider = imageProvider;
    }

    public void setOkListener(OkListener<T> listener) {
        this.listener = listener;
    }

    public void run(T modelObject) {
        this.modelObject = modelObject.copy();

        createUI();

        setModal(true);
        getRootPane().setDefaultButton(buttonOk);
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        buttonOk.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) { onOk(); }
        });

        buttonCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) { cancel(); }
        });

        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) { cancel(); }
        });

        contentPane.registerKeyboardAction(new ActionListener() {
            public void actionPerformed(ActionEvent e) { cancel(); }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        pack();
        setLocationRelativeTo(mainForm);
        if (firstFocusComponent != null) firstFocusComponent.requestFocusInWindow();
        setVisible(true);
    }

    protected void createUI() {
        buttonOk = new JButton("Ok");
        buttonCancel = new JButton("Cancel");

        contentPane = new JPanel();
        layout = new GroupLayout(contentPane);
        contentPane.setLayout(layout);
        layout.setAutoCreateContainerGaps(true);
    }

    protected void attach(JComponent component, ComponentController<T> controller) {
        controller.setDialogComponent(this, component);
        controller.addModelChangedListener(this);
        controller.setModelObject(modelObject);

        component.addFocusListener(controller);
    }

    protected void setButtonOKEnabled(boolean enabled) {
        buttonOk.setEnabled(enabled);
    }

    public void modelChanged(ComponentController... controllers) {
        // we only look at the last controller
        ComponentController controller = controllers[controllers.length-1];
        boolean valid = controller.isComponentValid();
        if (valid) {
            invalidControllers.remove(controller);
        } else {
            invalidControllers.add(controller);
        }

        JLabel label = getLabelFor(controller.getComponent());
        if (label != null) label.setForeground(valid ? Color.BLACK : Color.RED);

        boolean buttonOkWasEnabled = buttonOk.isEnabled();
        setButtonOKEnabled(invalidControllers.isEmpty());

        // special case: if we just enabled the button and the current focus owner is the buttonCancel
        // then we most likely skipped the buttonOk in a focus traversal because it was disabled
        // so simply request the focus back
        if (!buttonOkWasEnabled && invalidControllers.isEmpty()) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (buttonCancel.isFocusOwner()) buttonOk.requestFocusInWindow();
                }
            });
        }
    }

    protected void onOk() {
        if (invalidControllers.isEmpty()) {
            if (listener != null) listener.onOk(modelObject);
            dispose();
        } else {
            JOptionPane.showMessageDialog(this, "Please correct your entries!", "Illegal Values",
                    JOptionPane.ERROR_MESSAGE);
            JComponent component = invalidControllers.iterator().next().getComponent();
            if (component instanceof JTextComponent) ((JTextComponent) component).selectAll();
            component.requestFocusInWindow();
        }
    }

    public void cancel() {
        if (listener instanceof OkCancelListener) ((OkCancelListener<T>) listener).onCancel(modelObject);
        dispose();
    }

    private JLabel getLabelFor(Component component) {
        return getLabelFor(this, component);
    }

    // private helper
    private static JLabel getLabelFor(Container container, Component component) {
        for (Component c : container.getComponents()) {
            if (c instanceof JLabel && ((JLabel) c).getLabelFor() == component) {
                return (JLabel) c;
            }
            if (c instanceof Container) {
                JLabel label = getLabelFor((Container) c, component);
                if (label != null) return label;
            }
        }
        return null;
    }

}
